#include "Archivo.h"
#include "funciones.h"
#include <stdlib.h>
#include "Excepciones.h"
#include "funciones.h"

namespace Toolkit
{
Archivo::Archivo(const unsigned long int &tamanoUnidad, const string &ruta,
        const string &nombre, const TipoSerializacion &tserializacion,
        const stringstream &metadata,
        const TipoSerializacion &tSerializacionArchivosControl) :
    Almacenamiento(tamanoUnidad, tserializacion)
{
    construirArchivo(tamanoUnidad, ruta, nombre, tserializacion, metadata,
            tamanoUnidad, tSerializacionArchivosControl);
}

Archivo::Archivo(const unsigned long int &tamanoUnidad, const string& ruta,
        const string& nombre, const TipoSerializacion& tserializacion,
        const stringstream &metadata,
        const unsigned long int & tamanoUnidadPrimera,
        const TipoSerializacion &tSerializacionArchivosControl) :
    Almacenamiento(tamanoUnidad, tserializacion)
{
    construirArchivo(tamanoUnidad, ruta, nombre, tserializacion, metadata,
            tamanoUnidadPrimera, tSerializacionArchivosControl);
}

void Archivo::construirArchivo(const unsigned long int &tamanoUnidad,
        const string& ruta, const string& nombre,
        const TipoSerializacion& tserializacion, const stringstream &metadata,
        const unsigned long int & tamanoUnidadPrimera,
        const TipoSerializacion &tSerializacionArchivosControl)
{
    this->tSerializacionArchivosControl = tSerializacionArchivosControl;
    this-> ruta = ruta;
    this-> nombre = nombre;
    this->elementos = 0;
    this->extensionArchivo = this->obtenerExtension(tserializacion);
    this->extensionControl = this->obtenerExtension(
            tSerializacionArchivosControl);

    this->tamanoUnidadPrimera = tamanoUnidadPrimera;

    this->tamanoMetadata = metadata.str().size();

    this->stream = crearArchivo(ruta, nombre, "", tserializacion);

    fstream *fMetadata = NULL;

    if (tserializacion == BINARIO) {
        fMetadata = (fstream*) this->stream;
        this->offsetElementos = 5 * sizeof(unsigned long int)
                + metadata.str().size();
    }
    else if (tserializacion == TEXTO) {
        string nombrePreambulo = "";
        nombrePreambulo += ruta;
        nombrePreambulo += nombre;
        nombrePreambulo += "p";
        nombrePreambulo += extensionArchivo;

        fMetadata = new fstream(nombrePreambulo.c_str(), ios_base::out
                | ios_base::in | ios_base::trunc);

        this->offsetElementos = 0;
    }

    serializacionDigito((*fMetadata), tserializacion, this->tamanoUnidad);
    serializacionDigito((*fMetadata), tserializacion, this->tamanoUnidadPrimera);
    serializacionDigito((*fMetadata), tserializacion, this->offsetElementos);
    serializacionDigito((*fMetadata), tserializacion, this->elementos);

    serializacionDigito((*fMetadata), tserializacion, this->tamanoMetadata);
    fMetadata->write(metadata.str().c_str(), this->tamanoMetadata);

    if (tserializacion == TEXTO) {
        fMetadata->close();
        delete fMetadata;
    }
}

Archivo::Archivo(const string& ruta, const string& nombre,
        const TipoSerializacion& tserializacion,
        const TipoSerializacion &tSerializacionArchivosControl) :
    Almacenamiento(tserializacion)
{
    this->tSerializacionArchivosControl = tSerializacionArchivosControl;
    this->ruta = ruta;
    this->nombre = nombre;
    this->extensionArchivo = this->obtenerExtension(tserializacion);
    this->extensionControl = this->obtenerExtension(
            tSerializacionArchivosControl);

    string filename = ruta + nombre;
    string filenameYExtension = string(filename + this->extensionArchivo);
    fstream *fMetadata = NULL;

    if (tserializacion == BINARIO) {
        stream = new fstream(filenameYExtension.c_str(), ios_base::in
                | ios_base::out | ios_base::binary);
        fMetadata = (fstream*) this->stream;

        if (!(((fstream*) stream)->is_open())) {
            delete stream;
            throw ArchivoInexistenteException();
        }
    }
    else if (tserializacion == TEXTO) {
        stream = new fstream(filenameYExtension.c_str(), ios_base::in
                | ios_base::out);

        if (!((fstream*) stream)->is_open()) {
            delete stream;
            throw ArchivoInexistenteException();
        }

        string nombrePreambulo = "";
        nombrePreambulo += filename;
        nombrePreambulo += "p";
        nombrePreambulo += extensionArchivo;

        fMetadata = new fstream(nombrePreambulo.c_str(), ios_base::in);

    }

    hidratacionDigito((*fMetadata), tserializacion, this->tamanoUnidad);
    hidratacionDigito((*fMetadata), tserializacion, this->tamanoUnidadPrimera);
    hidratacionDigito((*fMetadata), tserializacion, this->offsetElementos);
    hidratacionDigito((*fMetadata), tserializacion, this->elementos);

    hidratacionDigito((*fMetadata), tserializacion, this->tamanoMetadata);

    if (tserializacion == TEXTO) {
        fMetadata->close();
        delete fMetadata;
    }

    string filenameNil = "";

    filenameNil += filename;
    filenameNil += ".nul";
    filenameNil += this->extensionControl;

    //se reconstruye el mapa de nulidad
    fstream nul(filenameNil.c_str(), ios_base::in | ios_base::out);

    nul.seekg(0, ios_base::beg);

    unsigned long int tamanoNul = 0;
    hidratacionDigito(nul, this->tSerializacionArchivosControl, tamanoNul);

    unsigned long int auxiliar = 0;

    for (unsigned long int i = 0; i < tamanoNul; i++) {
        hidratacionDigito(nul, this->tSerializacionArchivosControl, auxiliar);
        espacios.push_back(auxiliar);
    }

    nul.close();

    string filenameFrag = "";
    filenameFrag += filename;
    filenameFrag += ".frag";
    filenameFrag += this->extensionControl;

    fstream frag(filenameFrag.c_str(), ios_base::in);

    unsigned long int tamanoFrag = 0;
    hidratacionDigito(frag, this->tSerializacionArchivosControl, tamanoFrag);

    IndiceEspacio aux;
    aux.bytesDisponibles = 0;
    aux.posicion = 0;

    for (unsigned long int i = 0; i < tamanoFrag; i++) {
        hidratacionDigito(frag, this->tSerializacionArchivosControl,
                aux.bytesDisponibles);
        hidratacionDigito(frag, this->tSerializacionArchivosControl,
                aux.posicion);
        fragmentacion.push_back(aux);
    }

    frag.close();
}

Archivo::~Archivo()
{
    siExisteArchivoRemover(ruta, nombre, ".nul", tSerializacionArchivosControl);
    siExisteArchivoRemover(ruta, nombre, ".frag", tSerializacionArchivosControl);

    //se persiste el mapa de nulidad
    fstream* nul = crearArchivo(ruta, nombre, ".nul",
            this->tSerializacionArchivosControl);

    serializacionDigito(*nul, this->tSerializacionArchivosControl,
            espacios.size());

    while (!espacios.empty()) {
        unsigned long int aux = espacios.front();
        serializacionDigito((*nul), tSerializacionArchivosControl, aux);
        espacios.pop_front();
    }
    nul->close();
    delete nul;

    //se persiste el mapa de fragmentacion
    fstream* frag = crearArchivo(ruta, nombre, ".frag",
            this->tSerializacionArchivosControl);

    serializacionDigito(*frag, this->tSerializacionArchivosControl,
            fragmentacion.size());

    while (!fragmentacion.empty()) {
        IndiceEspacio aux = fragmentacion.front();

        serializacionDigito((*frag), tSerializacionArchivosControl,
                aux.bytesDisponibles);
        serializacionDigito((*frag), tSerializacionArchivosControl,
                aux.posicion);

        fragmentacion.pop_front();
    }

    frag->close();
    delete frag;

    fstream *fMetadata = NULL;

    if (tserializacion == BINARIO) {
        fMetadata = (fstream*) this->stream;
    }
    else if (tserializacion == TEXTO) {
        string nombrePreambulo = "";
        nombrePreambulo += ruta;
        nombrePreambulo += nombre;
        nombrePreambulo += "p";
        nombrePreambulo += extensionArchivo;

        fMetadata = new fstream(nombrePreambulo.c_str(), ios_base::in
                | ios_base::out);
    }

    unsigned long int moverPuntero = 0;

    fMetadata->seekg(ios_base::beg);
    for (int i = 0; i < 3; i++) {
        hidratacionDigito((*fMetadata), tserializacion, moverPuntero);
    }

    fMetadata->seekp(fMetadata->tellg());

    serializacionDigito((*fMetadata), tserializacion, this->elementos);

    if (tserializacion == TEXTO) {
        fMetadata->close();
        delete fMetadata;
    }

    ((fstream*) stream)->close();

    delete stream;
}

void Archivo::leerMetaData(stringstream &metadata)
{
    char *buffer = new char[this->tamanoMetadata];

    fstream *fMetadata = NULL;

    if (tserializacion == BINARIO) {
        fMetadata = (fstream*) this->stream;
    }
    else if (tserializacion == TEXTO) {
        string nombrePreambulo = "";
        nombrePreambulo += ruta;
        nombrePreambulo += nombre;
        nombrePreambulo += "p";
        nombrePreambulo += extensionArchivo;

        fMetadata = new fstream(nombrePreambulo.c_str(), ios_base::out
                | ios_base::in);
    }

    fMetadata->seekg(ios_base::beg);
    unsigned long int moverPuntero = 0;
    for (int i = 0; i < 5; i++) {
        hidratacionDigito((*fMetadata), tserializacion, moverPuntero);
    }

    fMetadata->read(buffer, this->tamanoMetadata);

    metadata.str("");
    metadata.write(buffer, this->tamanoMetadata);

    delete[] buffer;

    if (tserializacion == TEXTO) {
        fMetadata->close();
        delete fMetadata;
    }
}

void Archivo::actualizarMetadata(stringstream &metadata)
{

    char *buffer = new char[this->tamanoMetadata];

    fstream *fMetadata = NULL;

    if (tserializacion == BINARIO) {
        fMetadata = (fstream*) this->stream;
    }
    else if (tserializacion == TEXTO) {
        string nombrePreambulo = "";
        nombrePreambulo += ruta;
        nombrePreambulo += nombre;
        nombrePreambulo += "p";
        nombrePreambulo += extensionArchivo;

        fMetadata = new fstream(nombrePreambulo.c_str(), ios_base::out
                | ios_base::in);
    }

    fMetadata->seekg(ios_base::beg);
    unsigned long int moverPuntero = 0;
    for (int i = 0; i < 5; i++) {
        hidratacionDigito((*fMetadata), tserializacion, moverPuntero);
    }

    fMetadata->seekp(fMetadata->tellg());

    fMetadata->write(metadata.str().c_str(), this->tamanoMetadata);

    delete[] buffer;

    if (tserializacion == TEXTO) {
        fMetadata->close();
        delete fMetadata;
    }
}

fstream *Archivo::crearArchivo(const string &ruta, const string &nombre,
        const string &extension, const TipoSerializacion &tserializacion)
{
    fstream* returnAux = new fstream();

    if (returnAux == NULL) {
        return NULL;
    }

    //Crea el directorio
    if (ruta != "") {
        string commando = "mkdir -p ";
        commando.append(ruta);
        system(commando.c_str());
    }

    string rutaFinal = string(ruta).append(nombre);
    rutaFinal += extension;
    rutaFinal += this->obtenerExtension(tserializacion);

    if (tserializacion == TEXTO) {
        returnAux->open(rutaFinal.c_str(), ios_base::in | ios_base::out
                | ios_base::trunc);
    }
    else if (tserializacion == BINARIO) {

        returnAux->open(rutaFinal.c_str(), ios_base::in | ios_base::out
                | ios_base::binary | ios_base::trunc);
    }

    if (!returnAux->is_open()) {
        ofstream aux;
        aux.open(rutaFinal.c_str());
        if (aux.is_open()) {
            aux.close();
        }

        if (tserializacion == TEXTO) {
            returnAux->open(rutaFinal.c_str(), ios_base::in | ios_base::out
                    | ios_base::trunc);
        }
        else if (tserializacion == BINARIO) {
            returnAux->open(rutaFinal.c_str(), ios_base::in | ios_base::out
                    | ios_base::binary | ios_base::trunc);
        }

        if (!returnAux->is_open()) {
            throw NoSePuedeCrearArchivoException();
        }
    }

    return returnAux;
}

void Archivo::siExisteArchivoRemover(const string &ruta, const string &nombre,
        const string &extension, const TipoSerializacion &tSerializacion)
{
    string rutaYNombre = ruta;
    rutaYNombre += nombre;
    rutaYNombre += extension;

    obtenerExtension(tSerializacion);

    ifstream ifs(rutaYNombre.c_str());

    if (ifs) {
        if (ifs.is_open()) {
            ifs.close();
        }

        string commando = "rm " + rutaYNombre;
        system(commando.c_str());
    }
}

string Archivo::obtenerExtension(const TipoSerializacion &tSerializacion)
{
    if (tSerializacion == TEXTO) {
        return ".txt";
    }
    else if (tSerializacion == BINARIO) {
        return ".bin";
    }

    return "";
}

void Archivo::borrarContenido()
{
    stringstream metadata;
    leerMetaData(metadata);
    siExisteArchivoRemover(this->ruta, this->nombre, ".nul",
            this->tSerializacionArchivosControl);
    siExisteArchivoRemover(this->ruta, this->nombre, ".frag",
            this->tSerializacionArchivosControl);
    siExisteArchivoRemover(this->ruta, this->nombre, "",
            this->tSerializacionArchivosControl);

    this->elementos = 0;
    this->fragmentacion.clear();
    this->espacios.clear();

    this->stream = crearArchivo(this->ruta, this->nombre, "",
            this->tserializacion);

    fstream *fMetadata = NULL;

    if (this->tserializacion == BINARIO) {
        fMetadata = (fstream*) this->stream;
    }
    else if (this->tserializacion == TEXTO) {
        string nombrePreambulo = "";
        nombrePreambulo += ruta;
        nombrePreambulo += nombre;
        nombrePreambulo += "p";
        nombrePreambulo += extensionArchivo;

        fMetadata = new fstream(nombrePreambulo.c_str(), ios_base::out
                | ios_base::in | ios_base::trunc);
    }

    serializacionDigito((*fMetadata), this->tserializacion, this->tamanoUnidad);
    serializacionDigito((*fMetadata), this->tserializacion,
            this->tamanoUnidadPrimera);
    serializacionDigito((*fMetadata), this->tserializacion,
            this->offsetElementos);
    serializacionDigito((*fMetadata), this->tserializacion, this->elementos);

    serializacionDigito((*fMetadata), tserializacion, this->tamanoMetadata);
    fMetadata->write(metadata.str().c_str(), this->tamanoMetadata);

    if (tserializacion == TEXTO) {
        fMetadata->close();
        delete fMetadata;
    }
}

}
